// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package node

import (
	"fmt"

	"github.com/cockroachdb/errors"
	"github.com/dolthub/go-mysql-server/sql"
	"github.com/dolthub/go-mysql-server/sql/plan"
	vitess "github.com/dolthub/vitess/go/vt/sqlparser"

	"github.com/dolthub/doltgresql/core"
	"github.com/dolthub/doltgresql/core/extensions"
	"github.com/dolthub/doltgresql/core/functions"
	"github.com/dolthub/doltgresql/core/id"
	"github.com/dolthub/doltgresql/server/plpgsql"
	pgtypes "github.com/dolthub/doltgresql/server/types"
)

// CreateFunction implements CREATE FUNCTION.
type CreateFunction struct {
	FunctionName    string
	SchemaName      string
	Replace         bool
	ReturnType      *pgtypes.DoltgresType
	ParameterNames  []string
	ParameterTypes  []*pgtypes.DoltgresType
	Strict          bool
	Statements      []plpgsql.InterpreterOperation
	ExtensionName   string
	ExtensionSymbol string
	Definition      string
	SqlDef          string
	SqlDefParsed    vitess.Statement
	SetOf           bool
}

var _ sql.ExecSourceRel = (*CreateFunction)(nil)
var _ vitess.Injectable = (*CreateFunction)(nil)

// NewCreateFunction returns a new *CreateFunction.
func NewCreateFunction(
	functionName string,
	schemaName string,
	replace bool,
	retType *pgtypes.DoltgresType,
	paramNames []string,
	paramTypes []*pgtypes.DoltgresType,
	strict bool,
	definition string,
	extensionName string,
	extensionSymbol string,
	statements []plpgsql.InterpreterOperation,
	sqlDef string,
	sqlDefParsed vitess.Statement,
	setOf bool) *CreateFunction {
	return &CreateFunction{
		FunctionName:    functionName,
		SchemaName:      schemaName,
		Replace:         replace,
		ReturnType:      retType,
		ParameterNames:  paramNames,
		ParameterTypes:  paramTypes,
		Strict:          strict,
		Statements:      statements,
		ExtensionName:   extensionName,
		ExtensionSymbol: extensionSymbol,
		Definition:      definition,
		SqlDef:          sqlDef,
		SqlDefParsed:    sqlDefParsed,
		SetOf:           setOf,
	}
}

// Children implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Children() []sql.Node {
	return nil
}

// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateFunction) IsReadOnly() bool {
	return false
}

// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Resolved() bool {
	return true
}

// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateFunction) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
	idTypes := make([]id.Type, len(c.ParameterTypes))
	for i, typ := range c.ParameterTypes {
		idTypes[i] = typ.ID
	}
	funcCollection, err := core.GetFunctionsCollectionFromContext(ctx)
	if err != nil {
		return nil, err
	}
	paramTypes := make([]id.Type, len(c.ParameterTypes))
	for i, paramType := range c.ParameterTypes {
		paramTypes[i] = paramType.ID
	}
	schemaName, err := core.GetSchemaName(ctx, nil, c.SchemaName)
	if err != nil {
		return nil, err
	}
	funcID := id.NewFunction(schemaName, c.FunctionName, idTypes...)
	if c.Replace && funcCollection.HasFunction(ctx, funcID) {
		if err = funcCollection.DropFunction(ctx, funcID); err != nil {
			return nil, err
		}
	}
	var extName string
	if len(c.ExtensionName) > 0 {
		ext, err := extensions.GetExtension(c.ExtensionName)
		if err != nil {
			return nil, err
		}
		ident := extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion)
		_, err = extensions.GetExtensionFunction(extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion), c.ExtensionSymbol)
		if err != nil {
			return nil, err
		}
		extName = string(ident)
	}
	err = funcCollection.AddFunction(ctx, functions.Function{
		ID:                 funcID,
		ReturnType:         c.ReturnType.ID,
		ParameterNames:     c.ParameterNames,
		ParameterTypes:     paramTypes,
		Variadic:           false, // TODO: implement this
		IsNonDeterministic: true,
		Strict:             c.Strict,
		Definition:         c.Definition,
		ExtensionName:      extName,
		ExtensionSymbol:    c.ExtensionSymbol,
		Operations:         c.Statements,
		SQLDefinition:      c.SqlDef,
		SetOf:              c.SetOf,
	})
	if err != nil {
		return nil, err
	}
	return sql.RowsToRowIter(), nil
}

// Schema implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Schema() sql.Schema {
	return nil
}

// String implements the interface sql.ExecSourceRel.
func (c *CreateFunction) String() string {
	// TODO: fully implement this
	return "CREATE FUNCTION"
}

// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateFunction) WithChildren(children ...sql.Node) (sql.Node, error) {
	return plan.NillaryWithChildren(c, children...)
}

// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateFunction) WithResolvedChildren(children []any) (any, error) {
	if len(children) != 0 {
		return nil, ErrVitessChildCount.New(0, len(children))
	}
	return c, nil
}

// FunctionColumn represents the deferred column used in functions.
// It is a placeholder column reference later used for function calls.
type FunctionColumn struct {
	Name string
	Typ  *pgtypes.DoltgresType
	Idx  uint16
}

var _ vitess.Injectable = (*FunctionColumn)(nil)
var _ sql.Expression = (*FunctionColumn)(nil)

// Resolved implements the interface sql.Expression.
func (f *FunctionColumn) Resolved() bool {
	return !f.Typ.IsEmptyType()
}

// String implements the interface sql.Expression.
func (f *FunctionColumn) String() string {
	if f.Name != "" {
		return fmt.Sprintf(`$%v`, f.Idx+1)
	}
	return f.Name
}

// Type implements the interface sql.Expression.
func (f *FunctionColumn) Type() sql.Type {
	if f.Typ.IsEmptyType() {
		return pgtypes.Unknown
	}
	return f.Typ
}

// IsNullable implements the interface sql.Expression.
func (f *FunctionColumn) IsNullable() bool {
	return false
}

// Eval implements the interface sql.Expression.
func (f *FunctionColumn) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
	panic("FunctionColumn is a placeholder expression, but Eval() was called")
}

// Children implements the interface sql.Expression.
func (f *FunctionColumn) Children() []sql.Expression {
	return nil
}

// WithChildren implements the interface sql.Expression.
func (f *FunctionColumn) WithChildren(children ...sql.Expression) (sql.Expression, error) {
	if len(children) != 0 {
		return nil, sql.ErrInvalidChildrenNumber.New(f, len(children), 0)
	}
	return f, nil
}

// WithResolvedChildren implements the interface vitess.Injectable.
func (f *FunctionColumn) WithResolvedChildren(children []any) (any, error) {
	if len(children) != 0 {
		return nil, errors.Errorf("invalid FunctionColumn child count, expected `0` but got `%d`", len(children))
	}
	return f, nil
}
